#include "kernel.h"
#include "config.h"
#include "globals.h"
#include "errno.h"

#include "util/debug.h"
#include "util/list.h"
#include "util/string.h"
#include "util/printf.h"

#include "proc/kthread.h"
#include "proc/proc.h"
#include "proc/sched.h"
#include "proc/proc.h"

#include "mm/slab.h"
#include "mm/page.h"
#include "mm/mmobj.h"
#include "mm/mm.h"
#include "mm/mman.h"

#include "vm/vmmap.h"

#include "fs/vfs.h"
#include "fs/vfs_syscall.h"
#include "fs/vnode.h"
#include "fs/file.h"

proc_t *curproc = NULL; /* global */
static slab_allocator_t *proc_allocator = NULL;

static list_t _proc_list;
static proc_t *proc_initproc = NULL; /* Pointer to the init process (PID 1) */

void
proc_init()
{
        list_init(&_proc_list);
        proc_allocator = slab_allocator_create("proc", sizeof(proc_t));
        KASSERT(proc_allocator != NULL);
}

static pid_t next_pid = 0;

/**
 * Returns the next available PID.
 *
 * Note: Where n is the number of running processes, this algorithm is
 * worst case O(n^2). As long as PIDs never wrap around it is O(n).
 *
 * @return the next available PID
 */
static int
_proc_getid()
{
        proc_t *p;
        pid_t pid = next_pid;
        while (1) {
failed:
                list_iterate_begin(&_proc_list, p, proc_t, p_list_link) {
                        if (p->p_pid == pid) {
                                if ((pid = (pid + 1) % PROC_MAX_COUNT) == next_pid) {
                                        return -1;
                                } else {
                                        goto failed;
                                }
                        }
                } list_iterate_end();
                next_pid = (pid + 1) % PROC_MAX_COUNT;
                return pid;
        }
}

/*
 * The new process, although it isn't really running since it has no
 * threads, should be in the PROC_RUNNING state.
 *
 * Don't forget to set proc_initproc when you create the init
 * process. You will need to be able to reference the init process
 * when reparenting processes to the init process.
 */
proc_t *
proc_create(char *name)
{
	KASSERT(proc_allocator != NULL);
	
	proc_t *new_proc = NULL;
	new_proc = slab_obj_alloc(proc_allocator);
	if (new_proc == NULL) {
		return NULL;
	}

	new_proc->p_pid = _proc_getid();
	/*Yu: bug fixed in next 4 lines*/
	/*check pid*/
	if(new_proc->p_pid == -1){
	    slab_obj_free(proc_allocator, new_proc);
	    return NULL;
	}
	
	/* KASSERT required in grading guideline */
	KASSERT(PID_IDLE != new_proc->p_pid || list_empty(&_proc_list)); /* pid can only be PID_IDLE if this is the first process */
    	KASSERT(PID_INIT != new_proc->p_pid || PID_IDLE == curproc->p_pid); /* pid can only be PID_INIT when creating from*/
	
	if(new_proc->p_pid == PID_INIT)
		proc_initproc = new_proc;

	memcpy(new_proc->p_comm, name, PROC_NAME_LEN);

	list_init(&(new_proc->p_threads));
	list_init(&(new_proc->p_children));
	new_proc->p_pproc = curproc;

	new_proc->p_status = PROC_RUNNING;
	new_proc->p_state = 1;
	sched_queue_init(&(new_proc->p_wait));

	new_proc->p_pagedir = pt_create_pagedir();

	list_insert_head(proc_list(),
			 &(new_proc->p_list_link));
	if(curproc != NULL)
	{
		list_insert_head(&(curproc->p_children),
		         	&(new_proc->p_child_link));
	}

/*Yu: add code for VFS */
       /*init fd table*/
       memset(new_proc->p_files, NULL, sizeof(new_proc->p_files) );
       
       /*set current working dir */
      /*  struct vnode   *p_cwd;   */
      if( curproc != NULL ){
          new_proc->p_cwd = curproc->p_cwd;
      } else {
          new_proc->p_cwd = vfs_root_vn;
      }
      if(new_proc->p_cwd != NULL) {  
          vref(new_proc->p_cwd);
      }
/*Yu: end of code added for VFS*/

      /* VM related*/
      new_proc->p_brk = NULL;
      new_proc->p_start_brk = NULL; /* these two will be filled by loader */
      vmmap_t *new_vmmap = vmmap_create();
      if (new_vmmap == NULL) {
	      slab_obj_free(proc_allocator, new_proc);
	      return NULL;
      }
      new_proc->p_vmmap = new_vmmap;
      new_vmmap->vmm_proc = new_proc;
      
      return new_proc;
}

/**
 * Cleans up as much as the process as can be done from within the
 * process. This involves:
 *    - Closing all open files (VFS)
 *    - Cleaning up VM mappings (VM)
 *    - Waking up its parent if it is waiting
 *    - Reparenting any children to the init process
 *    - Setting its status and state appropriately
 *
 * The parent will finish destroying the process within do_waitpid (make
 * sure you understand why it cannot be done here). Until the parent
 * finishes destroying it, the process is informally called a 'zombie'
 * process.
 *
 * This is also where any children of the current process should be
 * reparented to the init process (unless, of course, the current
 * process is the init process. However, the init process should not
 * have any children at the time it exits).
 *
 * Note: You do _NOT_ have to special case the idle process. It should
 * never exit this way.
 *
 * @param status the status to exit the process with
 */
void
proc_cleanup(int status)
{

	/* KASSERT required in grading guideline */
	KASSERT(NULL != proc_initproc); /* should have an "init" process */
    	KASSERT(1 <= curproc->p_pid); /* this process should not be idle process */
    	KASSERT(NULL != curproc->p_pproc); /* this process should have parent process */

	/* closing all open files */
	int fd;
	for (fd = 0; fd < NFILES; fd++) {
		if (curproc->p_files[fd] != NULL) {
			do_close(fd);
		}
	}
	if (curproc->p_cwd != NULL) {
		vput(curproc->p_cwd);
	}

	if (curproc->p_vmmap != NULL) {
		dbgq(DBG_THR, "proc_cleanup();%s clean vmmap\n", curproc->p_comm);
		vmmap_destroy(curproc->p_vmmap);
	}
	/* reparenting any children to the init process */
	proc_t *child_proc = NULL;
	list_iterate_begin(&(curproc->p_children), 
			   child_proc,
			   proc_t,
			   p_child_link)
	{
		list_remove(&(child_proc->p_child_link));
		list_insert_head(&(proc_initproc->p_children),
				 &(child_proc->p_child_link));
		child_proc->p_pproc = proc_initproc;
	}
	list_iterate_end();

	/* setting the state and status */
	curproc->p_state = PROC_DEAD;
	curproc->p_status = status;
	curthr->kt_state = KT_EXITED;

	/* waking up its parent if it is waiting */
	proc_t *parent_proc = curproc->p_pproc;
	if(!sched_queue_empty(&(parent_proc->p_wait)))
	{
		sched_wakeup_on(&(parent_proc->p_wait));
	}
}

/*
 * This has nothing to do with signals and kill(1).
 *
 * Calling this on the current process is equivalent to calling
 * do_exit().
 *
 * In Weenix, this is only called from proc_kill_all.
 */
void
proc_kill(proc_t *p, int status)
{
	dbgq(DBG_THR, "proc_kill(): enter proc_kill(), curproc is %s\n", curproc->p_comm);
	dbgq(DBG_THR, "proc_kill(): want to kill proc: %s\n", p->p_comm);

	KASSERT((p != NULL) && (p != proc_initproc));
	
	kthread_t *thr;

	if(p == curproc)
		do_exit(status);
	else
	{
		/* if MTP, this function should kill all the thread
		 * in the process except the first one, let its
		 * parent do_waitpid and free all the resources.
		 */
		thr = list_head(&(p->p_threads), kthread_t, kt_plink);
		kthread_cancel(thr, NULL);
		/*
		
		??reparenting any children to the init process ??
		
		proc_t *child_proc = NULL;
		list_iterate_begin(&(p->p_children), 
			   	   child_proc,
			   	   proc_t,
			   	   p_child_link)
		{
			list_remove(&(child_proc->p_child_link));
			list_insert_head(&(proc_initproc->p_children),
				 	 &(child_proc->p_child_link));
		   	child_proc->p_pproc = proc_initproc;
		}
		list_iterate_end();

		??setting its status and state ??
		p->p_state = PROC_DEAD;
		p->p_status = status;

		?? waking up its parent if it is waiting ??
		proc_t *parent_proc = NULL;
		
		if(!sched_queue_empty(&(parent_proc->p_wait)))
			sched_wakeup_on(&(parent_proc->p_wait));
		
		*/
	}
}

/*
 * Remember, proc_kill on the current process will _NOT_ return.
 * Don't kill direct children of the idle process.
 *
 * In Weenix, this is only called by sys_halt.
 */
void
proc_kill_all()
{
	/* kill other process except process idle and init */
	proc_t *temp_proc = NULL;
	list_iterate_begin(proc_list(),
			   temp_proc,
			   proc_t,
			   p_list_link)
	{
		if((temp_proc != curproc) &&
		   (temp_proc != proc_initproc) &&
		   (temp_proc->p_pid != PID_IDLE))
		{
			proc_kill(temp_proc, 0);
		}
	}
	list_iterate_end();

	/* kill myself */
	if ((curproc != proc_initproc) && (curproc->p_pid != PID_IDLE)) {
		proc_kill(curproc, 0);
	}
}

proc_t *
proc_lookup(int pid)
{
        proc_t *p;
        list_iterate_begin(&_proc_list, p, proc_t, p_list_link) {
                if (p->p_pid == pid) {
                        return p;
                }
        } list_iterate_end();
        return NULL;
}

/* return the global process list */
list_t *
proc_list()
{
        return &_proc_list;
}

/*
 * This function is only called from kthread_exit.
 *
 * Unless you are implementing MTP, this just means that the process
 * needs to be cleaned up and a new thread needs to be scheduled to
 * run. If you are implementing MTP, a single thread exiting does not
 * necessarily mean that the process should be exited.
 */
void
proc_thread_exited(void *retval)
{
	/* single thread, just clean up and switch */
	proc_cleanup(0);
	sched_switch();
}

/* If pid is -1 dispose of one of the exited children of the current
 * process and return its exit status in the status argument, or if
 * all children of this process are still running, then this function
 * blocks on its own p_wait queue until one exits.
 *
 * If pid is greater than 0 and the given pid is a child of the
 * current process then wait for the given pid to exit and dispose
 * of it.
 *
 * If the current process has no children, or the given pid is not
 * a child of the current process return -ECHILD.
 *
 * Pids other than -1 and positive numbers are not supported.
 * Options other than 0 are not supported.
 */
pid_t
do_waitpid(pid_t pid, int options, int *status)
{
	proc_t *temp_proc = NULL;
	proc_t *child_proc = NULL;
	kthread_t *temp_thr = NULL;
	list_link_t *temp_link = NULL;
	pid_t child_pid = 0;
	int found_dead_child = 0;

	/*Yu: bug fixed in the next  lines*/
	/*check cancel flag*/
	if( curthr->kt_cancelled == 1 ){
	    kthread_exit(NULL);
	}
	
	KASSERT((pid == -1) || (pid > 0));
	KASSERT(options == 0);

	if(list_empty(&(curproc->p_children)))
		return -ECHILD;

	if(pid == -1)
	{
		while(!found_dead_child)
		{
			list_iterate_begin(&(curproc->p_children),
					   temp_proc,
					   proc_t,
					   p_child_link)
			{
				if(temp_proc->p_state == PROC_DEAD)
				{
					child_proc = temp_proc;
					temp_link = temp_proc->p_threads.l_next;
					temp_thr = list_item(temp_link, kthread_t, kt_plink);
					found_dead_child = 1;
					child_pid = temp_proc->p_pid;
					if(status != NULL)
						*status = temp_proc->p_status;

					break;
				}
			}
			list_iterate_end();

			temp_proc = child_proc;
			/* can not find a dead child */
			if(!found_dead_child)
			{
				if(-EINTR == sched_cancellable_sleep_on(&(curproc->p_wait)))
					kthread_exit(NULL);
			}
		}
	}
	else
	{
		temp_proc = proc_lookup(pid); /*get the wantted PCB*/
		if(temp_proc->p_pproc != curproc)
			return -ECHILD;
		
		while(temp_proc->p_state != PROC_DEAD)
		{
			/* if the given child is not dead, block */
			if(-EINTR == sched_cancellable_sleep_on(&(curproc->p_wait)))
				kthread_exit(NULL);
		}

		/* the given child is dead */
		temp_link = temp_proc->p_threads.l_next;
		temp_thr = list_item(temp_link, kthread_t, kt_plink);
		found_dead_child = 1;
		child_pid = pid;
		if(status != NULL)
			*status = temp_proc->p_status;
	}	

	dbgq(DBG_THR, "do_waitpid(): wakeup by child, curproc is %s\n", curproc->p_comm);
	
	/* KASSERT required in grading guideline */
	KASSERT(NULL != temp_proc); /* the process should not be NULL */
    	KASSERT(-1 == pid || temp_proc->p_pid == pid); /* should be able to find the process */
    	KASSERT(KT_EXITED == temp_thr->kt_state); /* thr points to a thread to be destroied */ 
    	KASSERT(NULL != temp_proc->p_pagedir); /* this process should have pagedir */
	
	/* remove child_proc from proc_list and parent`s children list */
	KASSERT(list_link_is_linked(&(temp_proc->p_child_link)));
	list_remove(&(temp_proc->p_child_link));
	KASSERT(list_link_is_linked(&(temp_proc->p_list_link)));
	list_remove(&(temp_proc->p_list_link));

	/* free PCB, TCB and stacks */
	kthread_destroy(temp_thr);
	slab_obj_free(proc_allocator, temp_proc);

	/*Yu: big fixed in next 4 lines:*/
	/*update next_pid to recycle PID resource*/
	if(next_pid > child_pid){
		next_pid = child_pid;
	}
	dbgq(DBG_THR, "do_waitpid(): leave do_waitpid(), curproc is %s\n", curproc->p_comm);
        return child_pid;
}

/*
 * Cancel all threads, join with them, and exit from the current
 * thread.
 *
 * @param status the exit status of the process
 */
void
do_exit(int status)
{
	/* since single thread, just clean up */
	proc_cleanup(status);
	sched_switch();
}

size_t
proc_info(const void *arg, char *buf, size_t osize)
{
        const proc_t *p = (proc_t *) arg;
        size_t size = osize;
        proc_t *child;

        KASSERT(NULL != p);
        KASSERT(NULL != buf);

        iprintf(&buf, &size, "pid:          %i\n", p->p_pid);
        iprintf(&buf, &size, "name:         %s\n", p->p_comm);
        if (NULL != p->p_pproc) {
                iprintf(&buf, &size, "parent:       %i (%s)\n",
                        p->p_pproc->p_pid, p->p_pproc->p_comm);
        } else {
                iprintf(&buf, &size, "parent:       -\n");
        }

#ifdef __MTP__
        int count = 0;
        kthread_t *kthr;
        list_iterate_begin(&p->p_threads, kthr, kthread_t, kt_plink) {
                ++count;
        } list_iterate_end();
        iprintf(&buf, &size, "thread count: %i\n", count);
#endif

        if (list_empty(&p->p_children)) {
                iprintf(&buf, &size, "children:     -\n");
        } else {
                iprintf(&buf, &size, "children:\n");
        }
        list_iterate_begin(&p->p_children, child, proc_t, p_child_link) {
                iprintf(&buf, &size, "     %i (%s)\n", child->p_pid, child->p_comm);
        } list_iterate_end();

        iprintf(&buf, &size, "status:       %i\n", p->p_status);
        iprintf(&buf, &size, "state:        %i\n", p->p_state);

#ifdef __VFS__
#ifdef __GETCWD__
        if (NULL != p->p_cwd) {
                char cwd[256];
                lookup_dirpath(p->p_cwd, cwd, sizeof(cwd));
                iprintf(&buf, &size, "cwd:          %-s\n", cwd);
        } else {
                iprintf(&buf, &size, "cwd:          -\n");
        }
#endif /* __GETCWD__ */
#endif

#ifdef __VM__
        iprintf(&buf, &size, "start brk:    0x%p\n", p->p_start_brk);
        iprintf(&buf, &size, "brk:          0x%p\n", p->p_brk);
#endif

        return size;
}

size_t
proc_list_info(const void *arg, char *buf, size_t osize)
{
        size_t size = osize;
        proc_t *p;

        KASSERT(NULL == arg);
        KASSERT(NULL != buf);

#if defined(__VFS__) && defined(__GETCWD__)
        iprintf(&buf, &size, "%5s %-13s %-18s %-s\n", "PID", "NAME", "PARENT", "CWD");
#else
        iprintf(&buf, &size, "%5s %-13s %-s\n", "PID", "NAME", "PARENT");
#endif

        list_iterate_begin(&_proc_list, p, proc_t, p_list_link) {
                char parent[64];
                if (NULL != p->p_pproc) {
                        snprintf(parent, sizeof(parent),
                                 "%3i (%s)", p->p_pproc->p_pid, p->p_pproc->p_comm);
                } else {
                        snprintf(parent, sizeof(parent), "  -");
                }

#if defined(__VFS__) && defined(__GETCWD__)
                if (NULL != p->p_cwd) {
                        char cwd[256];
                        lookup_dirpath(p->p_cwd, cwd, sizeof(cwd));
                        iprintf(&buf, &size, " %3i  %-13s %-18s %-s\n",
                                p->p_pid, p->p_comm, parent, cwd);
                } else {
                        iprintf(&buf, &size, " %3i  %-13s %-18s -\n",
                                p->p_pid, p->p_comm, parent);
                }
#else
                iprintf(&buf, &size, " %3i  %-13s %-s\n",
                        p->p_pid, p->p_comm, parent);
#endif
        } list_iterate_end();
        return size;
}
